//Class Person
class Person {
	private String name;
	private int age;

	public String getName() {...}
	public void setName(String name) {...}
	public int getAge() {...}
	public void setAge(int age) {...}
}

//Class Student
class Student {
	private String name;
	private int age;
	private int score;

	public String getName() {...}
	public void setName(String name) {...}
	public int getAge() {...}
	public void setAge() {...}

	//class Student 专有的Method
	public int getScore() {...}
	public void setScore() {...}
}

//继承,可以避免重复代码,1个class属于另一个class的抽象
//Java使用extends关键字来实现继承：
class Person {
    private String name;
    private int age;

    public String getName() {...}
    public void setName(String name) {...}
    public int getAge() {...}
    public void setAge(int age) {...}
}

class Student extends Person {
	//不要重复name和age的field/method
	//只需要定义新增score的field/method
	private int score;

	public int getScore() {...} 
	public void setScore(int score) {...}
}

//可见，通过继承，Student只需要编写额外的功能，不再需要重复代码。

//在OOP的术语中，我们把Person称为'超类（super class），父类（parent class），基类（base class）'，把Student称为'子类（subclass），扩展类（extended class）'。

/*
继承树
注意到我们在定义Person的时候，没有写extends。在Java中，没有明确写extends的类，编译器会自动加上extends Object。所以，任何类，除了Object，都会继承自某个类。下图是Person、Student的继承树：

┌───────────┐
│  Object   │
└───────────┘
      ▲
      │
┌───────────┐
│  Person   │
└───────────┘
      ▲
      │
┌───────────┐
│  Student  │
└───────────┘
Java只允许一个class继承自一个类，因此，一个类有且仅有一个父类。只有Object特殊，它没有父类。

类似的，如果我们定义一个继承自Person的Teacher，它们的继承树关系如下：

       ┌───────────┐
       │  Object   │
       └───────────┘
             ▲
             │
       ┌───────────┐
       │  Person   │
       └───────────┘
          ▲     ▲
          │     │
          │     │
┌───────────┐ ┌───────────┐
│  Student  │ │  Teacher  │
└───────────┘ └───────────┘
*/


// protected
//继承有个特点，就是子类无法访问父类的private字段或者private方法。例如，Student类就无法访问Person类的name和age字段

class Person {
    private String name;
    private int age;
}

class Student extends Person {
    public String hello() {
        return "Hello, " + name; // 编译错误：无法访问name字段
    }
}

//这使得继承的作用被削弱了。为了让子类可以访问父类的字段，我们需要把private改为protected。用protected修饰的字段可以被子类访问

class Person { // field运用protected
    protected String name; 
    protected int age;
}

class Student extends Person {
    public String hello() {
        return "Hello, " + name; // OK!
    }
}

//因此，protected关键字可以把字段和方法的访问权限控制在继承树内部，一个protected字段和方法可以被其子类，以及子类的子类所访问，后面我们还会详细讲解。


// super
// super关键字表示父类（超类）。子类引用父类的字段时，可以用super.fieldName。
class Studentextends Person {
	public String hello() {
		return "Hello," + super.name;
	}
}
//实际上，这里使用super.name，或者this.name，或者name，效果都是一样的。编译器会自动定位到父类的name字段。


//但是，在某些时候，就必须使用super。我们来看一个例子：
public class Main {
	public static void main(String[] args) {
		Student s = new Student("Xiao Ming", 12, 89);
	}
}

class Person {
	protected String name;
	protected int age;

	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}
}

class Student extends Person {
	protected int score;

	public Student(String name, int age, int score) {
		this.score = score;
	}
}
//运行上面的代码，会得到一个编译错误，大意是在Student的构造方法中，无法调用Person的构造方法。

//这是因为在Java中，任何class的构造方法，第一行语句必须是调用父类的构造方法。如果没有明确地调用父类的构造方法，编译器会帮我们自动加一句super();，所以，Student类的构造方法实际上是这样:
class Student extends Person {
    protected int score;

    public Student(String name, int age, int score) {
        super(); // 自动调用父类的构造方法
        this.score = score;
    }
}
//但是，Person类并没有无参数的构造方法，因此，编译失败。

//小总结:子类被new的时候,其会自动调用super()去调用‘父类’的‘无参构造函数’

//解决方法是调用Person类存在的某个构造方法。例如：
class Student extends Person {
	protected int score;

	public Student(String name, int age, int score) {
		super(name,age); //手动指定:调用父类的构造method->Person(String,int)
	}
}
//这样就可以正常编译了！


//因此我们得出结论：如果父类没有默认的构造方法，子类就必须显式调用super()并给出参数以便让编译器定位到父类的一个合适的构造方法。

//顺带引出了另一个问题：即子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的，不是继承的。



//向上转型
//如果一个引用变量的类型是Student，那么它可以指向一个Student类型的实例
Student s = new Student();

//如果一个引用类型的变量是Person，那么它可以指向一个Person类型的实例：
Person p = new Person();

//如果Student是从Person继承下来的，那么，一个引用类型为Person的变量，能否指向Student类型的实例？
Person p = new Student(); //???
//测试一下就可以发现，这种指向是允许的！
//重点:看new Student(),右边属于instance,Person是super类

//这是因为Student继承自Person，因此，它拥有Person的全部功能。Person类型的变量，如果指向Student类型的实例，对它进行操作，是没有问题的！

//一个子类类型安全地变为父类类型的赋值，被称为向上转型（upcasting）。


//向上转型实际上是把一个子类型安全地变为更加抽象的父类型：
Student s = new Student();
Person p = s; // upcasting, ok

Object o1 = p; // upcasting, ok
Object o2 = s; // upcasting, ok

//注意到继承树是Student > Person > Object，所以，可以把Student类型转型为Person，或者更高层次的Object。





//向下转型
//和向上转型相反，如果把一个父类类型强制转型为子类类型，就是向下转型（downcasting）。例如：
Person p1 = new Student(); // upcasting, ok
Person p2 = new Person();
Student s1 = (Student) p1; // ok,p1从Person->Student,因为p1是new Student()
Student s2 = (Student) p2; // runtime error! ClassCastException!,因为p2是new Person()

//Person类型p1实际指向Student实例，Person类型变量p2实际指向Person实例。在向下转型的时候，把p1转型为Student会成功，因为p1确实指向Student实例，把p2转型为Student会失败，因为p2的实际类型是Person，不能把父类变为子类，因为子类功能比父类多，多的功能无法凭空变出来。

//因此，向下转型很可能会失败。失败的时候，Java虚拟机会报ClassCastException。



//为了避免向下转型出错，Java提供了instanceof操作符，可以先判断一个实例究竟是不是某种类型：
Person p = new Person();
System.out.println(p instanceof Person); //true
System.out.println(p instanceof Student); //false

Student s = new Student();
System.out.println(s instanceof Person); //true
System.out.println(s instanceof Student);//true

Student n = null;
System.out.println(n instanceof Student);//false

//instanceof实际上判断一个变量所指向的实例是否是指定类型，或者这个类型的子类。如果一个引用变量为null，那么对任何instanceof的判断都为false。

//利用instanceof，在向下转型前可以先判断：
Person p = new Student();

if (p instanceof Student) {
	//只有判断成功,才会cast to Student
	Student s = (Student) p; //一定会成功
}


//从Java 14开始，判断instanceof后，可以直接转型为指定变量，避免再次强制转型。例如，对于以下代码：
Object obj = "hello";
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println(s.toUpperCase());
}
//可以改写成： obj instanceof String s
public class Main {
    public static void main(String[] args) {
        Object obj = "hello";
        if (obj instanceof String s) {
            // 可以直接使用变量s:
            System.out.println(s.toUpperCase());
        }
    }
}
//使用instanceof variable这种'判断并转型为指定类型变量'的语法时，必须打开编译器开关--source 14和--enable-preview。(--源 14 & --预先编译)



//区分 继承 和 组合
//使用继承的时候,我们要注意逻辑一致性
class Book {
	protected String name;

	public String getName() {...}
	public void setName(String name) {...}
}

//这个class Book也有field name,那么能不能让Student继承自Book呢？
class Student extends Book {
	protected int score;
}
//显然，从逻辑上讲，这是不合理的，Student不应该从Book继承，而应该从Person继承。

//是因为Student是Person的一种，它们是is关系，而Student并不是Book。实际上Student和Book的关系是has关系。



//具有has关系不应该使用继承，而是使用组合，即Student可以持有一个Book实例：
//class组合(class持有)
class Student extends Person {
	protected Book book;//持有一个Book类的instance book
	protected int score;
}

//因此,继承是is关系,组合是has关系



//练习
//定义PrimaryStudent，从Student继承，并新增一个grade字段：
public class Main {
    public static void main(String[] args) {
        Person p = new Person("小明", 12);
        Student s = new Student("小红", 20, 99);
        // TODO: 定义PrimaryStudent，从Student继承，新增grade字段:
        Student ps = new PrimaryStudent("小军", 9, 100, 5);
        System.out.println(ps.getScore());
    }
}

class Person {
    protected String name;
    protected int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
}

class Student extends Person {
    protected int score;

    public Student(String name, int age, int score) {
        super(name, age);
        this.score = score;
    }

    public int getScore() { return score; }
}

class PrimaryStudent extends Student {
    // TODO
    protected int grade;

    public void setGrade(int grade) {
    	this.grade = grade;
    } 

    public int getGrade() {
    	return this.grade;
    }
}

/*
小结
继承是面向对象编程的一种强大的代码复用方式；

	1.Java只允许单继承，所有类最终的根类是Object；

	2.protected允许子类访问父类的field(字段)和method(方法);

	3.子类的构造方法可以通过super()调用父类的构造方法;

	4.可以安全地向上转型为更抽象的类型;

	5.可以强制向下转型，最好借助instanceof判断;
	(JDK 14 可以 x instanceof Type y同时向下转型)

	6.子类和父类的关系是is，has关系不能用继承。
*/




